home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1995 / MacHack 1995.toast / Presentations / Presentations ’90 / TDynamicPopup ASCII backup < prev    next >
Text File  |  1990-04-25  |  38KB  |  737 lines

  1. MacApp supports several Macintosh user interface devices that allow a user to choose one item from a number of items.  These devices include pull-down menus for choosing commands, the standard get-file dialog (SFGetFile) for choosing files, radio button groups for choosing among exclusive options, and checkboxes for choosing among non-exclusive options.
  2.  
  3. In this vein, MacApp also provides the TPopup class.  When used as directed, TPopup provides a user-interface device for choosing among a fixed number of static items.  But what if you want to give the user a choice among non-static items--items that vary in number and whose names can change at runtime?  TPopup does not support this kind of dynamic behavior.
  4.  
  5. You could try a SFGetFile-type dialog, but that would mean losing the directness of TPopup’s click-and-choose behavior.  You could use a scrollable list, but then you would lose TPopup’s visual compactness.  So, to get the small screen space of a TPopup control and the dynamic listing behavior of a scrollable list, we want to create a subclass of TPopup, add some methods, and override the methods that get in our way.
  6.  
  7. Review: What’s a TPopup?
  8. For those of you new to object-oriented programming with MacApp, let me provide some background.  TPopup is one of MacApp’s many predefined classes.  TPopup implements the standard Apple popup menu as defined in Inside Macintosh and Apple’s book “Human Interface Guidelines: The Apple Desktop Interface” (Addison-Wesley, 1987).
  9.  
  10. TPopup has its own methods (functionality) and fields (data), but it also inherits methods and fields from its ancestor classes.  Moving up the hierarchy, TPopup’s ancestors are TControl, TView, TEvtHandler (i.e., TEventHandler), and TObject (from which  all objects in MacApp derive).
  11.  
  12. Thus, TPopup is an object that can
  13. • handle events through its inherited TEvtHandler functionality,
  14. • activate or deactivate itself, show or hide itself, and enable or disable itself through its inherited TView functionality,
  15. • dim or undim itself, track mouse clicks and mouse movement, and return a TCommand object through its inherited TControl functionality, and
  16. • draw a popup menu in response to a mouse click, return the currently selected item number or text, and set the item selection to a given value through its own methods.
  17. In other words, TPopup is a specialized TControl, which is a specialized TView, which is a specialized TEvtHandler.
  18.  
  19. Review: How Do You Customize TPopup’s Behavior?
  20. Just as TPopup is a specialized TControl, TDynamicPopup is a specialized TPopup, because TDynamicPopup inherits from (or “is a subclass of”) TPopup.  Therefore, the way you customize the behavior of TPopup is to define a new class that is a subclass of TPopup.
  21.  
  22. As part of this new class, you define new methods and fields that are required to provide the desired behavior but which are lacking in TPopup (and its ancestors).  
  23.  
  24. In some cases, you will want to modify the behavior of a method inherited from TPopup or one of TPopup’s ancestors.  To do so, you define a replacement method that overrides the inherited method.  If you want to add functionality to an inherited method, you override it and call it directly (using the “Inherited” keyword) from within the new method’s implementation.
  25.  
  26. Introducing TDynamicPopup
  27. TDynamicPopup is a subclass of TPopup that implements a popup menu for a dynamic list of items.  In addition to standard TPopup methods, TDynamicPopup provides methods for 
  28. • adding, removing, and altering items while your application is running,
  29. • selecting an item by its name or relative to the current selection, and
  30. • returning the current number (count) of menu items, the cleared status of the menu (whether or not the menu has zero items), and whether a popup menu hit is actually an item-selection change.
  31.  
  32. When would you use a dynamic popup menu (“popmenu” for short)?  Let me answer this through the use of a hypothetical application.  Consider an analysis application in which the user can create and name any number of analysis components.  Suppose that each such component has a number of values and attributes that are shown and editable in TDialogView views that take up a whole window.
  33.  
  34. Now, we want to give the user to be able to see and edit the values and attributes of any component in any order.  We need a way to let the user pick among all the components quickly without having to remember or type names (of course).  A dynamic popmenu is a user interface device that implements the desired functionality.
  35.  
  36. Using the TDynamicPopup Class
  37. First, a caveat: the code given in the listings in this article is not quite commercial quality.  It works well, but there are a few visual blemishes that I haven’t resolved yet.  The “Blemishes” section later in this paper describes the problems.
  38.  
  39. In general, you can use a TDynamicPopup instance wherever you can use a TPopup instance.  Usually, a TDynamicPopup control is a subview within a TDialogView instance.
  40.  
  41. Listing A shows the declaration for the TDynamicPopup class.  The three instance variables (fields) are only for use within TDynamicPopup; consider them private.  Among the methods, AdjustBotRight and DoRedraw should be considered private, but all the rest are public (non-TDynamicPopup code can use them freely).
  42.  
  43. The TDynamicPopup code assumes that you are creating views using resource templates.  The next section describes how to specify the resources for TDynamicPopup instances.  The subsequent sections describe TDynamicPopup methods, the implementations for which are given in Listing B.
  44.  
  45. Specifying TDynamicPopup Resources
  46. To use a TDynamicPopup in your application, you can start with a dummy 'cmnu' resource as illustrated in Listing 1.  This 'cmnu' resource is a dummy because you don’t specify any actual menu items; the actual menu items are established during program execution.  Still, you must define a 'cmnu' resource to use the existing TPopup code.
  47.  
  48. Next, for each TDynamicPopup instance desired, define a Popup 'view' resource as in Listing 2.  For the details of the TPopup resource specification, see the MacApp documentation, the DemoDialogs example application (supplied with MacApp), and the ViewTypes.r resource definition file.
  49.  
  50. The last number in the Popup 'view' resource represents the “Item left offset.”  This number reserves horizontal space for the popmenu title, which, in turn, is specified in the 'cmnu' resource.  
  51.  
  52. Listing 1 specifies a blank menu title (" ") and Listing 2 specifies that zero space be allowed for the popmenu’s title.  You can use this approach, along with a TStaticText instance for the actual popmenu title, when you want a popmenu whose title doesn’t dim when you dim and disable the popmenu.  (I dim and disable my popmenus when they have zero items.)  However, if you use a TStaticText instance for the popmenu title, it will not highlight when the popmenu is showing as called for in the Apple Desktop Interface guidelines.
  53.  
  54. When you do want the title to dim with the popmenu itself, define a 'cmnu' resource with the desired title and specify the appropriate offset in the corresponding Popup 'view' resource. 
  55.  
  56. TDynamicPopup uses the width specification in the Popup 'view' resource  in a special way.  The AdjustBotRight method uses this value (‘140’ in Listing 2) as the maximum width for the “current selection” box of the popmenu; the popmenu code will not grow the current selection box wider than this maximum width.
  57.  
  58. Initializing a TDynamicPopup Instance
  59. The TDynamicPopup code assumes that the resource-based TDynamicPopup instances are created as part of a NewTemplateWindow call.  Thus, each TDynamicPopup instance automatically gets an IRes message.  The TDynamicPopup code overrides the TPopup.IRes method to initialize the fields specific to TDynamicPopup (see Listing B).
  60.  
  61. Adding Menu Items
  62. The AddPopItem, AppendPopItem, and CopyItemsFrom methods create menu items for a TDynamicPopup instance (see Listing B).
  63. For AddPopItem, if the parameter afterItemIdx is greater than the number of items in the menu, AddPopItem defaults to AppendPopItem.  Otherwise, AddPopItem inserts the new item after the afterItemIdx'th item.  If afterItemIdx is 0, the item is inserted before the first item in the menu. 
  64.  
  65. AppendPopItem puts itemText at the end of the popmenu.  If there are no actual items in the menu, AppendPopItem inserts itemText as the first and only item.
  66.  
  67. CopyItemsFrom copies the items of a specified TDynamicPopup to SELF.  This method is useful if you want to have two independent popmenus showing the same items.  With independent popmenus, the user can select different items for different purposes.  For example, the user could select one item as a component to edit now, and select a different item as a default item to use in a simulation later.
  68.  
  69. Removing Menu Items
  70. To remove items singly, use RemovePopItem.  RemovePopItem returns True if the item was actually removed.  RemovePopItem returns False if the popmenu is empty or the itemIdx parameter is greater than the number items in the menu.
  71.  
  72. If the item removed was the currently selected item, RemovePopItem sets the new item selection to the preceding item; if the deleted item was the first item, RemovePopItem sets the item selection to the new first item.
  73.  
  74. To remove all items at once, use ClearMenuItems.
  75.  
  76. Whenever a TDynamicPopup instance is cleared (i.e., there are no actual menu items to show), it displays a place-holder item such as “(none)”.  TDynamicPopup uses the string in global variable kvNoSelectionStr as the place-holder item.  
  77.  
  78. Typically, you disable and dim a TDynamicPopup instance when it is cleared.  In a perfect world, the code to disable/dim and enable/undim the popup view would be part of the TDynamicPopup methods that remove and add items.  If this were the case, then the disable/dim and enable/undim response would be transparent to the code that uses TDynamicPopup instances.  Unfortunately, achieving the correct redrawing of controls is sometimes tricky; I have had better luck keeping such code at a higher level.
  79.  
  80. Changing an Item’s Text
  81. To change an item’s text, use ChangeItemText.  This method automatically adjusts the size of the popmenu to accommodate the change.
  82.  
  83. Changing the Current Selection
  84. To change the current selection, you can use SelectItemRelative, SetCurrentItem, or SetCurrentItemByStr.  SelectItemRelative changes the selection to the item after the current selection if its parameter next is True, otherwise it changes the selection to the previous item.  This method treats the first and last items in a popmenu as adjacent; the item “after” the last item is the first item and vice versa.  Also, SelectItemRelative returns the text of the newly selected item in the variable parameter theItem.  This method is useful if you want to offer a command-key shortcut for selecting the next or previous item in a TDynamicPopup.
  85.  
  86. The SetCurrentItem method overrides the TPopup version to set the fOldCurrentItem field.  Unfortunately, the TPopup version of SetCurrentItem does not call Focus before it attempts to redraw; TView.AssumeFocus catches this mistake in debugging mode.  To add the Focus call, TDynamicPopup.SetCurrentItem includes all the code in TPopup.SetCurrentItem.  But this means the code cannot support color, because a required  procedure, GetMenuColors, is not available outside of UDialog (i.e., GetMenuColors is not declared in the interface of UDialog).  Of course, you can get around this problem by copying GetMenuColors into the unit containing the TDynamicPopup code or by putting its declaration in UDialog.p, thus modifying MacApp; I chose to live without color for now.
  87.  
  88. SetCurrentItemByStr sets the selected item of a TDynamicPopup instance by name instead of by number.  In practice, this capability comes in handy.
  89.  
  90. Utility Methods
  91. GetItemCount simply returns the current number of items in the TDynamicPopup instance.
  92.  
  93. IsCleared returns True if there are no actual items (i.e., just the place-holder item) in the TDynamicPopup instance.
  94.  
  95. ItemSelectionChanged returns True if the currently selected item is different from fOldCurrentItem, which represents the last recognized item selection.  
  96.  
  97. Typically, you use ItemSelectionChanged in a DoChoice implementation.  For example, when you get a mPopupHit and identify the originating view (origView) as a TDynamicPopup that you handle, send it a ItemSelectionChanged query.  If the result is True, then the mPopupHit was something more than just a click on the current selection.  In this way you can use ItemSelectionChange to avoid processing a popmenu selection that didn’t really change the current selection.
  98.  
  99. The DoRedraw method is used in a number of the other TDynamicPopup methods.  It redraws the popmenu in a way that avoids the GetMenuColors procedure (as described above for SetCurrentItem).  You shouldn’t need to use DoRedraw outside of the TDynamicPopup code.
  100.  
  101. The AdjustBotRight method determines the size of the current selection box of the popmenu.  The reasons for overriding the TPopup version of this method are explained in the next section.
  102.  
  103. Blemishes
  104. The TDynamicPopup code has a couple of small visual problems.  First, I haven't taken the time to figure out how to properly fix a certain redrawing glitch, so the current selection box only grows (up to a limit) and never shrinks.
  105.  
  106. Normally, TPopup.AdjustBotRight sizes the current selection box to accommodate the longest item text in the menu.  In TDynamicPopup, the longest item can change.  In particular, it can get shorter, which is a possibility that TPopup.AdjustBotRight ignores.  As a result, TPopup.AdjustBotRight doesn’t always erase a previous current selection box completely; it can leave part of a previously-selected item showing when it draws a new current selection box for a shorter menu item.
  107.  
  108. After a limited effort to fix the problem, I kludged my way out.  I overrode AdjustBotRight and modified the code so it only grows the current selection box (i.e., it never shrinks the current selection box).  My modification limits this growth to the width specified in the view template for the popmenu.
  109.  
  110. This kludge leads to another quirk (which is less glaring…maybe): when the longest item name is replaced by a shorter name, the popmenu code draws the actual menu narrower than the current selection box.  
  111.  
  112. Some day I’ll get around to fixing this redrawing problem properly: if you come up with a fix, please let me know.
  113.  
  114. The second quirk stems from Toolbox-related mysteries.  The TDynamicPopup code goes to some effort to prevent the removal of the last remaining item in a popmenu.  Such a deletion (by the Toolbox call DelMenuItem) causes a great crash, showing video flames on the monitor.
  115.  
  116. I tried to turn this problem into a feature: whenever there are no items in the popmenu, TDynamicPopup displays “(none)” as the current selection.  Displaying “(none)” is more explicit than just showing a blank, right?
  117.  
  118. Wrapping Up
  119. The code for TDynamicPopup will (hopefully) appear in an upcoming MacApp Developers Association Goodies disk.  In addition to the source code for TDynamicPopup, the Goodies disk will include a small demo application.  The demo application will illustrate how to use the various TDynamicPopup methods and what the final result looks like.
  120.  
  121. If you have any comments or questions about this paper or about TDynamicPopup, please contact me by mail at 4635 McGuiness, Dexter, MI 48130, or via Compuserve at 71101,3675.
  122.  
  123.  
  124. Bibliography
  125.  
  126. The following is an informal list of articles and books relating to object-oriented programming and MacApp.  I hope you find it useful.
  127.  
  128. Books:
  129. Cox, Brad J.   Object Oriented Programming: an Evolutionary Approach.  Reading MA:  Addison-Wesley, 1986.   (Focuses on Objective C.)
  130.  
  131. Meyer, Bertrand.   Object-Oriented Software Construction.  Englewood Cliffs, NJ: Prentice-Hall, Inc., 19??  (Focuses on the language Eiffel.)
  132.  
  133. Schmucker, Kurt J.  Object-Oriented Programming.  Hasbrouck Heights, NJ: Hayden Book Company, 1986.  (Provides a good introduction to MacApp, albeit an early version of MacApp.)
  134.  
  135. Stroustrup, Bjarne.  The C++ Programming Language.  Reading, MA: Addison-Wesley Publishing Co., 1986.
  136.  
  137. Wilson, David A., Larry S.Rosenstein, and Dan Shafer.  Programming with MacApp.  Reading, MA: Addison-Wesley Publishing Co., 1990.  (The latest--second!--book on MacApp, released at the 1990 San Francisco MacWorld Expo.  Part of a new series called “Macintosh Inside Out”, edited by Scott Knaster.)
  138.  
  139. Articles:
  140.  
  141. from APDAlog:
  142.  
  143. West, Joel.  “Mastering MPW: OOP(s) with C++,”  APDAlog, Fall 1989, p8.  (Interesting comparisons between Object Pascal, C++, and Think C; also describes how Object Pascal, C++, and C implementations compare, and SADE support.)
  144.  
  145. Katz, Howard.  “OOP Notes: Something Special,”  APDAlog, Fall 1989, p13. (a "report" on AAPPS’ MicroTV.)
  146.  
  147. Katz, Howard.  “OOP Notes: It's Hot!,”  APDAlog, Summer 1989, p14. (exhorts programmers to learn OOP)
  148.  
  149. Burbeck, Steve.  “Object-Oriented Programming, A History,”  APDAlog, Winter 1989, p17.  (see also page 14)
  150.  
  151. references from AppleDTS Tech Note #239, “Inside Object Pascal”:
  152.  
  153. •  Inside Macintosh, Volume II-53, The Segment Loader
  154. •  The Complete MacTutor, Volume 2, “Introduction to Object Pascal”, p. 336
  155. •  Macintosh Technical Note #105, MPW Object Pascal Without MacApp
  156. •  Macintosh Technical Note #110, MPW:  Writing Stand-Alone Code
  157. •  Macintosh Technical Note #220, Segment Loader Limitations
  158.  
  159. from Apple Direct:
  160.  
  161. Williams, Gregg.  “Designing the Future: The Power of Object-Oriented Programming,”  Apple Direct, Feb. 1989, p6.
  162.  
  163. Williams, Gregg.  “(Slightly) Inside MacApp,”  Apple Direct, May 1989, p12. (Includes a figure of the MacApp class hierarchy.)
  164.  
  165.  
  166. from BYTE:
  167.  
  168. ??.  “SmallTalk-80 for Mac,”  Byte, Jan. 1989, p143.
  169.  
  170. Thomas, Dave.  “What's in an Object?,”  Byte, Mar. 1989, p231.
  171.  
  172. Wegner, Peter.  “Learning the Language,”  Byte, Mar. 1989, p245.  (Discusses OOP in general, touches on several OOP languages.)
  173.  
  174. Dodani, Hughes, Moshell.  “Separation of Powers,”  Byte, Mar. 1989, p255.   (Discusses MacApp and other toolkits.)
  175.  
  176. Thompson, Tom.  “The Next Step,”  Byte, Mar. 1989, p265.  (Discusses NeXT's NextStep environment, shows class hierarchy.)
  177.  
  178. from MacTech Quarterly:
  179. Katz, Howard.  “Highly Objectionable: The Think C Library” (a column),  MacTech Quarterly, Winter 1989, p102.
  180.  
  181. Knaster, Scott.  “??” (part of an article presenting prognostications),  MacTech Quarterly, Spring 1989, p14.  (Comments from Scott Knaster promoting OOP.)
  182.  
  183. Johnson, Ted.  “??” (part of an article presenting prognostications),  MacTech Quarterly, Spring 1989, p17.  (Comments on using OOP to create software components by Ted Johnson of Aldus.)
  184.  
  185. Leonard, Randy.  “OOP: The Future for Macintosh Development, An Introduction to Object-Oriented Programming,”  MacTech Quarterly, Spring 1989, p22.
  186.  
  187. Salmons, Jim, and Timlynn Babitsky.  “Prograph: A Turtle Geometric Introduction,”  MacTech Quarterly, Spring 1989, p52.
  188.  
  189. Witten, Steve.  “Programming in C++, A Linguistic History,”  MacTech Quarterly, Summer 1989, p18.  (A good intro to C++.)
  190.  
  191. Storrie-Lombardi, Michael C.  “Smalltalk/V Mac: A New Standard in Object Oriented Programming,”  MacTech Quarterly, Summer 1989, p90.
  192.  
  193.  
  194. from MacTutor:
  195.  
  196. Szpakowski, Mark.  “MacOOPs!: Prograph raises OOP to new height,”  MacTutor, July 1989, p66.  (Prograph is verrry interesting; read this article for a good intro.)
  197.  
  198. Dallas, Alastair.  “MacOOPs!: A First Look at THINK C 4.0,”  MacTutor, October 1989, p69.
  199.  
  200. Smith, David.  “MacOOPs!: Introduction to MacApp,”  MacTutor, Aug. 1989, p10.
  201.  
  202. Treister, Adam.  “C Objects: Pseudo Objects,”  MacTutor, Aug. 1989, p38.  (the “POOPShell”)
  203.  
  204. McMath, Chuck and Carl Nelson.  “MacApp Workshop: a tale of two quadratic plotters,”  MacTutor, Aug. 1989, p56.
  205.  
  206. Muys-Vasovic, Jean-Denis.  “MacOOPs!: Back to the Future: OOP & MacApp,”  MacTutor, Sept. 1989, p102.
  207.  
  208. Doyle, Ken.  “Introduction to Object Pascal,” anthologized in The Complete MacTutor, Volume 2  p. 336
  209.  
  210. from MacWorld:
  211. Meng, Brita.  “Object-Oriented Programming,”  MacWorld, Jan. 1990, p174.
  212.  
  213. ??.  “C++ and MacApp 2.0,”  MacWorld, April 1989.
  214.  
  215. Listing A
  216.  
  217. TDynamicPopup = Object( TPopup )
  218.  
  219.     fOldCurrentItem    : Integer;    {xx}
  220.         { holds the last previous current item # to permit checking for a change }
  221.     fMaxWidth                : Integer;    {xx}
  222.     fOldWidth                : Integer;    {xx}
  223.  
  224.     PROCEDURE TDynamicPopup.IRes(itsDocument: TDocument; itsSuperView: TView; 
  225.         VAR itsParams: Ptr); OVERRIDE;
  226.     PROCEDURE TDynamicPopup.AddPopItem( afterItemIdx:Integer;
  227.         itemText:Str255; redraw:Boolean );
  228.     PROCEDURE TDynamicPopup.AdjustBotRight; OVERRIDE;
  229.     PROCEDURE TDynamicPopup.AppendPopItem( itemText:Str255; redraw:Boolean );
  230.     PROCEDURE TDynamicPopup.ChangeItemText( idx:Integer; itemText:Str255;
  231.         redraw : Boolean );
  232.     PROCEDURE TDynamicPopup.ClearMenuItems( redraw:Boolean );
  233.     PROCEDURE TDynamicPopup.CopyItemsFrom( fromPopup:TDynamicPopup;
  234.         setName:Str255; VAR nameFound:Boolean; redraw:Boolean );
  235.     Procedure TDynamicPopup.DoRedraw;
  236.     Function  TDynamicPopup.GetItemCount : Integer;
  237.     Function  TDynamicPopup.IsCleared : Boolean;
  238.     Function  TDynamicPopup.ItemSelectionChanged : Boolean;
  239.     Function  TDynamicPopup.RemovePopItem( itemIdx:Integer; redraw:Boolean )
  240.         : Boolean;
  241.     Procedure TDynamicPopup.SelectItemRelative( next:Boolean;
  242.         VAR theItem:Str255 );
  243.     PROCEDURE TDynamicPopup.SetCurrentItem (item:INTEGER; redraw:BOOLEAN);
  244.         OVERRIDE;
  245.     PROCEDURE TDynamicPopup.SetCurrentItemByStr( theStr:Str255; VAR item:Integer;
  246.         VAR found:Boolean; redraw:Boolean );
  247. End;
  248.  
  249.  
  250. Listing B
  251.  
  252. (************************************************)
  253. (* - T D y n a m i c  P o p u p                 *)
  254. (************************************************)
  255.  
  256. { ---------------------- •    IRes -----------------------------------}
  257. {$S DlogRKRes}
  258. Procedure TDynamicPopup.IRes(itsDocument:TDocument; itsSuperView:TView; VAR itsParams: Ptr); OVERRIDE;
  259. Begin
  260.     fMaxWidth := ViewTemplatePtr(itsParams)^.itsSize.h;
  261.     fOldCurrentItem := 1;
  262.     fOldWidth                := 0;
  263.     INHERITED IRes(itsDocument, itsSuperView, itsParams );
  264. End;
  265.  
  266.  
  267. { ---------------------- •    AddPopItem -----------------------------------}
  268. {$S DlogRKRes}
  269. Procedure TDynamicPopup.AddPopItem( afterItemIdx:Integer; itemText:Str255; redraw:Boolean );
  270. Begin
  271.     If afterItemIdx > CountMItems( fMenuHandle ) Then
  272.         AppendPopItem( itemText, redraw )
  273.     Else
  274.         Begin
  275.         { - must call InsMenuItem and then SetItem in case itemText contains 
  276.             meta chars [';','^','!','<','/','('] -- SetItem doesn't convert metas.
  277.         }
  278.             InsMenuItem( fMenuHandle, kDummyStr, afterItemIdx );
  279.             SetItem( fMenuHandle, afterItemIdx+1, itemText );
  280.             
  281.             AdjustBotRight;
  282.             If redraw & Focus Then DoRedraw;
  283.         End;
  284. End; { TDynamicPopup.AddPopItem }
  285.  
  286. { ---------------------- •    AdjustBotRight -----------------------------------}
  287. {$S DlogRKRes}
  288. Procedure TDynamicPopup.AdjustBotRight; OVERRIDE;
  289. VAR
  290.     numItems, newHeight, newWidth : Integer;
  291.     savedPort            : GrafPtr;
  292.     theFontInfo        : FontInfo;
  293.  
  294. Begin
  295.     If fMenuHandle <> NIL Then
  296.         Begin
  297.             CalcMenuSize(fMenuHandle);
  298.             numItems := CountMItems(fMenuHandle);
  299.             newWidth := Max( fOldWidth, fMenuHandle^^.menuWidth+fItemOffset+fInset.left+fInset.right+3);
  300.             newWidth := Min( newWidth, fMaxWidth );
  301.             fOldWidth:= newWidth;
  302.     
  303.             GetPort(savedPort);
  304.             SetPort(gWorkPort);
  305.             SetPortTextStyle(gSystemStyle);
  306.             GetFontInfo(theFontInfo);
  307.             SetPort(savedPort);
  308.     
  309.             WITH theFontInfo DO
  310.                 newHeight := ascent + descent + leading + fInset.top + fInset.bottom + 3;
  311.     
  312.             Resize(newWidth, newHeight, kDontRedraw);
  313.         End;
  314. End;
  315.  
  316. { ---------------------- •    AppendPopItem -----------------------------------}
  317. {$S DlogRKRes}
  318. Procedure TDynamicPopup.AppendPopItem( itemText:Str255; redraw:Boolean );
  319. Begin
  320. { - if the current menu has no actual items, replace the 'none' item with itemText: }
  321.     If IsCleared Then
  322.         ChangeItemText( 1, itemText, redraw )
  323.     Else
  324.         Begin
  325.         { - must call InsMenuItem and then SetItem in case itemText contains 
  326.             meta chars [';','^','!','<','/','('] -- SetItem doesn't convert metas.
  327.         }
  328.             AppendMenu( fMenuHandle, kDummyStr );
  329.             SetItem( fMenuHandle, CountMItems( fMenuHandle ), itemText );
  330.                 
  331.             AdjustBotRight;        { fix popmenu box for new menu item }
  332.             If redraw & Focus Then DoRedraw;
  333.         End;
  334. End; { TDynamicPopup.AppendPopItem }
  335.  
  336. { ---------------------- •    ChangeItemText -----------------------------------}
  337. {$S DlogRKRes}
  338. Procedure TDynamicPopup.ChangeItemText( idx:Integer; itemText:Str255; redraw:Boolean );
  339. Begin
  340.     SetItem( fMenuHandle, idx, itemText );
  341.     AdjustBotRight;
  342.     If redraw & Focus Then DoRedraw;
  343. End; { TDynamicPopup.ChangeItemText }
  344.  
  345.  
  346. { ---------------------- •    TDynamicPopup.ClearMenuItems -------------------}
  347. {$S DlogRKRes}
  348. Procedure TDynamicPopup.ClearMenuItems( redraw:Boolean );
  349. Var idx : Integer;
  350. Begin
  351.     For idx := CountMItems( fMenuHandle ) DownTo 2 Do
  352.         DelMenuItem( fMenuHandle, idx );
  353.     ChangeItemText( 1, kvNoSelectionStr, kDontRedraw );
  354.     SetCurrentItem( 1, redraw );
  355. End;
  356.  
  357. { ---------------------- •    TDynamicPopup.CopyItemsFrom -------------------}
  358. {$S DlogRKRes}
  359. Procedure TDynamicPopup.CopyItemsFrom( fromPopup:TDynamicPopup; setName:Str255;
  360.                                                                                 VAR nameFound:Boolean; redraw: Boolean );
  361. Var
  362.     idx, dummyItem    : Integer;
  363.     aStr, currName    : Str255;
  364.     wasFound                : Boolean;
  365. Begin
  366.     If StringIsEmpty( setName ) Then
  367.         GetItemText( GetCurrentItem, currName )
  368.     Else
  369.         currName := setName;
  370.     
  371. { - remove current items: }
  372.     ClearMenuItems( kDontRedraw );
  373. { - add items from 'fromPopup': }
  374.     For idx := 1 To CountMItems( fromPopup.fMenuHandle ) Do
  375.         Begin
  376.             fromPopup.GetItemText( idx, aStr );
  377.             AppendPopItem( aStr, kDontRedraw );
  378.         End;
  379. { - attempt to set current item # by setting current item to currName: }
  380.     SetCurrentItemByStr( currName, dummyItem, nameFound, redraw );
  381. { - if could not find 'currName' in 'fromPopup', set to 1: }
  382.     If Not nameFound Then SetCurrentItem( 1, redraw );
  383. End;
  384.  
  385. { ---------------------- • TDynamicPopup.DoRedraw ---------------------------}
  386. {$S DlogRKRes}
  387. Procedure TDynamicPopup.DoRedraw;
  388. Var menuRect,labelRect:Rect;
  389. Begin
  390.     GetQDExtent(menuRect);
  391.     
  392. { would want to provide color support here but...
  393.         cannot do color because GetMenuColors is not declared in the interface
  394.         for UDialog.  Could either copy GetMenuColors from UDialog.inc1.p into this
  395.         unit, or could modify UDialog.p to declare GetMenuColors for public consumption.
  396.         For now, just skip it.
  397. }
  398.     CalcLabelRect(labelRect);
  399.     UnionRect( menuRect, labelRect, labelRect );
  400.     Draw( labelRect );
  401. End; { TDynamicPopup.DoRedraw }
  402.  
  403.  
  404. { ---------------------- • TDynamicPopup.GetItemCount ------------------------}
  405. {$S DlogRKRes}
  406. Function TDynamicPopup.GetItemCount : Integer;
  407. Begin
  408.     If IsCleared Then
  409.         GetItemCount := 0
  410.     Else
  411.         GetItemCount := CountMItems(fMenuHandle);
  412. End;
  413.  
  414. { ---------------------- • TDynamicPopup.IsCleared --------------------------}
  415. {$S DlogRKRes}
  416. Function TDynamicPopup.IsCleared : Boolean;
  417. Var item1 : Str255;
  418. Begin
  419.     If CountMItems(fMenuHandle) = 1 Then
  420.         Begin
  421.             GetItemText( 1, item1 );
  422.             IsCleared := IUCompString( item1, kvNoSelectionStr) = 0;
  423.         End
  424.     Else
  425.         IsCleared := False;
  426. End;
  427.  
  428. { ---------------------- •    ItemSelectionChanged -------------------------}
  429. {$S DlogRKRes}
  430. Function TDynamicPopup.ItemSelectionChanged : Boolean;
  431. Begin
  432.     ItemSelectionChanged := GetCurrentItem <> fOldCurrentItem;
  433. End;
  434.  
  435. { ---------------------- •    TDynamicPopup.RemovePopItem -----------------------}
  436. {$S DlogRKRes}
  437. Function TDynamicPopup.RemovePopItem( itemIdx:Integer; redraw:Boolean ) : Boolean;
  438. Var currItem:Integer;
  439. Begin
  440.     If IsCleared Then
  441.         RemovePopItem := False
  442.     Else If itemIdx > CountMItems( fMenuHandle ) Then
  443.         Begin
  444.             If qDebug Then
  445.                 ProgramBreak( 'RemovePopItem: trying to remove an item beyond the last one!!');
  446.             RemovePopItem := False;
  447.         End
  448.     Else
  449.         Begin
  450.             currItem := GetCurrentItem;                        { remember the current item # }
  451.             DelMenuItem( fMenuHandle, itemIdx );    { delete the chosen item }
  452.  
  453.         { - if the item removed was the current item, set the item selection
  454.                 to the item that preceeded the deleted item (or to item #1 if
  455.                 the deleted item was item #1):
  456.         }
  457.             If itemIdx = currItem Then
  458.                 If itemIdx > 1 Then
  459.                     SetCurrentItem( itemIdx - 1, redraw )
  460.                 Else
  461.                     SetCurrentItem( 1, redraw );
  462.             
  463.             RemovePopItem := True;            { success }
  464.         End;
  465. End; { TDynamicPopup.RemovePopItem }
  466.  
  467.  
  468. { ---------------------- • TDynamicPopup.SelectItemRelative -----------------}
  469. {$S DlogRKRes}
  470. Procedure TDynamicPopup.SelectItemRelative( next:Boolean; VAR theItem:Str255 );
  471. Var item, itemCount    : Integer;
  472. Begin
  473.     item          := GetCurrentItem;
  474.     itemCount := CountMItems( fMenuHandle );
  475.  
  476.     If itemCount > 1 Then
  477.         Begin
  478.             If next Then
  479.                 If item = itemCount Then
  480.                 { - at last item in popmenu; move to the first item: }
  481.                     item := 1
  482.                 Else
  483.                 { - move to the next obj: }
  484.                     item := item + 1
  485.             Else { move previous: }
  486.                 If item = 1 Then
  487.                 { - at first item in popmenu; move to the last item: }
  488.                     item :=  itemCount
  489.                 Else
  490.                 { - move to the previous obj: }
  491.                     item := item - 1;
  492.  
  493.             { - set the name popup to the previous/next item: }
  494.                 SetCurrentItem( item, kRedraw );
  495.             End;
  496.             
  497. { - get the newly-selected item: }
  498.     GetItemText( GetCurrentItem, theItem );
  499. End;    { TDynamicPopup.SelectItemRelative }
  500.  
  501. { ---------------------- •    SetCurrentItem -----------------------------------}
  502.  
  503.     { need to override SetCurrentItem because:
  504.         - want to set fOldCurrentItem (which is TDynamicPopup-specific)
  505.         - in the redraw portion of TPopup.SetCurrentItem, there is a missing Focus call
  506.             (the application fails AssumeFocused in debug mode without the Focus call as
  507.             shown below)
  508.         Cannot do color because GetMenuColors is not declared in the interface
  509.         for UDialog.  Could either copy GetMenuColors from UDialog.inc1.p into this
  510.         unit, or could modify UDialog.p to declare GetMenuColors for public consumption.
  511.         For now, just skip it.
  512.     }
  513.  
  514. {$S DlogRKRes}
  515. Procedure TDynamicPopup.SetCurrentItem (item: Integer; redraw: Boolean); OVERRIDE;
  516. Var
  517.     chkdItem:Integer;
  518.     menuRect:Rect;
  519.     newFColor,newBkColor:RGBColor;
  520. Begin
  521.     fOldCurrentItem := GetCurrentItem;
  522.  
  523.     If (fMenuHandle <> NIL) & (item <> fCurrentItem) Then
  524.         Begin
  525.             If fCurrentItem <> 0 Then
  526.                 SetItemMark(fMenuHandle, fCurrentItem, ' ');
  527.             
  528.             chkdItem := Max( 1, Min( item, CountMItems( fMenuHandle ) ) ); {paranoia}
  529.             If item <> 0 Then
  530.                 SetItemMark(fMenuHandle, chkdItem, CHR(checkMark));
  531.             fCurrentItem := chkdItem;
  532.         End;
  533.  
  534.     If redraw & Focus Then
  535.         Begin
  536.             GetQDExtent(menuRect);
  537.         {
  538.             GetMenuColors(menuRect, fMenuID, item, newFColor, newBkColor);
  539.             SetIfColor(newFColor); SetIfBkColor(newBkColor);
  540.         }
  541.             DrawPopupBox(menuRect);
  542.         End;
  543. End;    { TDynamicPopup.SetCurrentItem }
  544.  
  545.  
  546. { ---------------------- •    SetCurrentItemByStr ---------------------------}
  547. {$S DlogRKRes}
  548. Procedure TDynamicPopup.SetCurrentItemByStr( theStr:Str255; VAR item:Integer; 
  549.                                                                                             VAR found:Boolean; redraw:Boolean );
  550. Var
  551.     idx : Integer;
  552.     aStr: Str255;
  553. Begin
  554.     For idx := 1 To CountMItems( fMenuHandle ) Do
  555.         Begin
  556.             GetItemText( idx, aStr );
  557.             If IUCompString( aStr, theStr ) = 0 Then
  558.                 Begin
  559.                     SetCurrentItem(idx, redraw );
  560.                     item := idx;
  561.                     found := True;
  562.                     Exit( SetCurrentItemByStr );
  563.                 End;
  564.         End;
  565.  
  566. { - no match found }
  567.     item := 1;
  568.     found:= False;
  569.     SetCurrentItem(1, redraw );
  570. End;
  571.  
  572.  
  573.  
  574.  
  575.  
  576.     ----------    Footnotes    ----------    
  577.  
  578.     1.    For references to books and articles on MacApp and object programming, see the bibliography at the end of this paper.
  579.  
  580.     2.    All MacApp object class names begin with “T” by convention.  Although the “T” is arbitrary, the convention is useful because it provides instant recognition of object class names in source code and elsewhere.
  581.  
  582.     3.    Defining a new class in Object Pascal is not too different from defining a new Record in Pascal.  The first line of the definition is “TDynamicPopup = Object( TPopup),” which specifies that TDynamicPopup is a subclass of TPopup.  See the appropriate MPW Pascal or Think Pascal documentation for further details.
  583.  
  584.     4.    If you are not familiar with the concepts of overriding methods or calling an inherited method, see one of the general descriptions of MacApp listed in the bibliography.
  585.  
  586.     5.    If you are not familiar with the technique of creating views from resource templates in MacApp, look it up in the MacApp documentation or in Programming with MacApp by Dave Wilson, et al (see the bibliography).  This technique is one of MacApp’s strong points.
  587.  
  588.     6.    The current selection box is the text box that shows the current selection when the actual popmenu is not showing.
  589.  
  590.     7.    For an explanation of why there is a maximum width, see “Blemishes” later in this article.
  591.  
  592.     8.    For an explanation of the need for a placeholder item, see “Blemishes” later in this article.
  593.  
  594.     9.    I’m not sure that this problem still exists; recent experimentation seemed to indicate that deleting the last item was OK.  Perhaps a recent MacOS update fixed the problem.
  595.  
  596.     10.    Actually, "(none)" can be whatever string you define for the global variable kvNoSelectionStr.
  597.  
  598.  
  599.  
  600.     ----------    Sidebars    ----------    
  601.  
  602. Dynamic Popup Menus in MacApp 2.0ß9
  603. Ralph Krug, P.E.
  604.  
  605. Abstract: 
  606. This paper describes a new class for use with MacApp.  The class, called TDynamicPopup, implements a popup menu that can show a dynamic list of items for selection.  TDynamicPopup is a subclass of MacApp’s TPopup class, which implements a popup menu with a static list of items.  This paper provides a simple example of object-oriented programming with MacApp.
  607.  
  608. Listing 1
  609. resource 'cmnu' (kDummyPopup) {
  610.   kDummyPopup, textMenuProc, allEnabled, enabled, " ",
  611.     { "xxx", noIcon, "", "", plain, nocommand }
  612. };
  613.  
  614. Listing 2
  615. 'sprV', 'PopV', { 20,57 }, { 20,140 },     sizeVariable, sizeVariable, shown, enabled,
  616. Popup { "TDynamicPopup", noAdornment, sizeable,     notDimmed, notHilited, doesntDismiss, noInset,     systemFont, kDummyPopup, 1, 0 },
  617.  
  618.  
  619.  
  620.     ----------    Posted Notes    ----------    
  621.  
  622. Listing 1
  623.  
  624. resource 'cmnu' (kDummyPopup) {
  625.   kDummyPopup, textMenuProc, allEnabled, enabled, " ",
  626.     { "xxx", noIcon, "", "", plain, nocommand }
  627. };
  628.  
  629. Listing 2
  630. 'naCl', 'NamP', { 20,57 }, { 20,140 },     sizeVariable, sizeVariable, shown, enabled,
  631. Popup { "TDynamicPopup", noAdornment, sizeable,     notDimmed, notHilited, doesntDismiss, noInset,     systemFont, kDummyPopup, 1, 0 },
  632.  
  633. Procedure SetCtrlEnable( aControl:TControl; doEnable, redraw:Boolean );
  634. Begin
  635.     If doEnable = aControl.isDimmed Then
  636.         Begin
  637.         { - change enable status only if aControl is NOT a TStaticText/TCluster instance: }
  638.             If (Member(aControl, TEditText))     { NB: TEditText is subclass of TStaticText }
  639.             |  Not (Member(aControl, TStaticText) | Member(aControl, TCluster))
  640.             Then
  641.                 aControl.ViewEnable( doEnable, kDontRedraw );
  642.             aControl.DimState( Not doEnable, redraw );
  643.         End;
  644. End;
  645.  
  646.  
  647. AddPopItem( afterItemIdx : Integer; itemText : Str255; redraw:Boolean );
  648. AppendPopItem( itemText : Str255; redraw : Boolean );
  649. CopyItemsFrom( fromPopup:TDynamicPopup; setName:Str255;
  650.  
  651. if 'afterItemIdx' is greater than the number of items in the menu, AddPopItem defaults to AppendPopItem; otherwise, AddPopItem inserts the new item after the 'afterItemIdx'th item.  If afterItemIdx is 0, the item is inserted before the first item in the menu.
  652.  
  653. For AppendPopItem, if the popmenu has no actual items (just the dummy string kvNoSelectionStr), AppendPopItem replaces the dummy string with itemText.   Otherwise, AppendPopItem adds itemText after the current last item.  In both cases, it adjusts the menu to accomodate the new/changed itemtext.
  654.  
  655. RemovePopItem( itemIdx : Integer; redraw : Boolean ) : Boolean;
  656. ClearMenuItems( redraw: Boolean );
  657.  
  658. --altering:
  659. ChangeItemText( idx:Integer; itemText : Str255; redraw : Boolean );
  660.  
  661. Procedure TDynamicPopup.ChangeItemText( idx :Integer; itemText:Str255; redraw:Boolean );
  662. Begin
  663.     SetItem( fMenuHandle, idx, itemText );
  664.     AdjustBotRight;
  665.     If redraw & Focus Then DoRedraw;
  666. End;
  667.  
  668. --selection:
  669. SelectItemRelative( next:Boolean; VAR theItem:Str255 );
  670. SetCurrentItem (item: INTEGER; redraw: BOOLEAN); OVERRIDE;
  671. SetCurrentItemByStr( theStr:Str255; VAR item:Integer; 
  672.  
  673.  
  674. Procedure TDynamicPopup.SetCurrentItem (item: Integer; redraw: Boolean); OVERRIDE;
  675. VAR chkdItem            : Integer;
  676. Begin
  677.     fOldCurrentItem := GetCurrentItem;
  678.  
  679.     If (fMenuHandle <> NIL) & (item <> fCurrentItem) Then
  680.         Begin
  681.             If fCurrentItem <> 0 Then
  682.                 SetItemMark(fMenuHandle, fCurrentItem, ' ');
  683.             
  684.             chkdItem := Max( 1, Min( item, CountMItems( fMenuHandle ) ) );
  685.             If item <> 0 Then
  686.                 SetItemMark(fMenuHandle, chkdItem, CHR(checkMark));
  687.             fCurrentItem := chkdItem;
  688.         End;
  689.         
  690.     If redraw & Focus Then DoRedraw;
  691. End;
  692.  
  693. Currently, SetCurrentItem also avoids the TPopup colorizing code that tries to color the menu and its items.  The colorizing code colors items by their position within the menu, which, in a TDynamicPopup instance, can change at any time.  Thus, adding color to an item in a TDynamicPopup instance doesn’t really add much information (although it might be fun anyway).  However, adding color capability to the other components of the popmenu (the title, bar color, the items as a whole, and so on; see IM V-239) should be supported.
  694.  
  695. --query methods:
  696. GetItemCount : Integer;
  697. IsCleared : Boolean;
  698. ItemSelectionChanged : Boolean;
  699.  
  700. --utility:
  701. AdjustBotRight; OVERRIDE;
  702. DoRedraw;
  703.  
  704.  
  705.         mPopupHit:
  706.             If (origView = fNamePopup) & (fNamePopup.ItemSelectionChanged) Then 
  707.  
  708.  
  709. Procedure TDynamicPopup.AdjustBotRight; OVERRIDE;
  710. VAR
  711.     numItems, newHeight, newWidth : Integer;
  712.     savedPort            : GrafPtr;
  713.     theFontInfo        : FontInfo;
  714.  
  715. Begin
  716.     If fMenuHandle <> NIL Then
  717.         Begin
  718.             CalcMenuSize(fMenuHandle);
  719.             numItems := CountMItems(fMenuHandle);
  720.             newWidth := Max( fOldWidth, fMenuHandle^^.menuWidth+fItemOffset+fInset.left+fInset.right+3);
  721.             newWidth := Min( newWidth, fMaxWidth );
  722.             fOldWidth:= newWidth;
  723.     
  724.             GetPort(savedPort);
  725.             SetPort(gWorkPort);
  726.             SetPortTextStyle(gSystemStyle);
  727.             GetFontInfo(theFontInfo);
  728.             SetPort(savedPort);
  729.     
  730.             WITH theFontInfo DO
  731.                 newHeight := ascent + descent + leading + fInset.top + fInset.bottom + 3;
  732.     
  733.             Resize(newWidth, newHeight, kDontRedraw);
  734.         End;
  735. End;
  736.  
  737.